module.exports = GCanvas;

var Path = require('./path')
  , Motion = require('./motion')
  , GcodeDriver = require('./drivers/gcode')
  , NullDriver = require('./drivers/null')
  , Point = require('./math/point')
  , Matrix = require('./math/matrix')
  , Font = require('./font')
  , parseFont = require('./parsefont')
  , utils = require('./utils');

// 宽度和高度暂时没用上
function GCanvas(driver, width, height) {
  var self = this;
  this.canvas = {
    width: width,
    height: height,
    getContext: function() {
      return self;
    }
  };

  if(driver === null) {
    driver = new NullDriver();
  }

  this.font = "7pt Helvetiker";
  this.matrix = new Matrix();
  this.rotation = 0; 
  this.speed = 0;
  this.speedMax = 0;
  this.depthOfCut = 0;
  this.align = 'center';
  this.driver = driver || new GcodeDriver();
  this.driver.src = this;
  this.stack = [];
  this.motion = new Motion(this);
  
  this.filters = [];
  this.precision = 20;
  this.ramping = true;
  this.fillRule = 'evenodd';
  this.strokeStyle = '#000000'; // 描边颜色设置
  this.fillStyle = '#000000'; // 填充颜色设置
  this.fillDir = 0; // 填充方向
  this.fillAngle = 0; // 填充角度
  this.bounds = {
    left: Infinity,
    top: -Infinity,
    right: -Infinity,
    bottom: Infinity
  };

  // if(this.driver.init)
  //   this.driver.init();

  this.beginPath();
}

GCanvas.prototype = {
  save: function() {
    this.stack.push({
      matrix: this.matrix.clone(),
      font: this.font,
      speed: this.speed,
      feed: this.feed,
      speedMax: this.speedMax,
      toolDiameter: this.toolDiameter,
      align: this.align,
      filters: this.filters.slice(),
      strokeStyle: this.strokeStyle,
      fillStyle: this.fillStyle
    });
  }
, clone: function(driver, width, height) {
    var copy = new GCanvas(driver, width, height);
    copy.matrix = this.matrix.clone();
    copy.font = this.font;
    copy.speed = this.speed;
    copy.feed = this.feed;
    copy.speedMax = this.speedMax;
    copy.toolDiameter = this.toolDiameter;
    copy.align = this.align;
    copy.filters = this.filters.slice();
    copy.strokeStyle = this.strokeStyle;
    copy.fillStyle = this.fillStyle;
    copy.optimizeParams = Object.assign({}, this.optimizeParams);// 浅拷贝
    copy.rotateMotionParams = Object.assign({}, this.rotateMotionParams);

    return copy;
  }
, restore: function() {
    var prev = this.stack.pop();
    for(var k in prev) {
      if(prev.hasOwnProperty(k)) {
        this[k] = prev[k];
      }
    }
  }
// , setOffset: function(offset) {
//   this.motion.setOffset(offset);
// }
, setSpeedMax: function(speed) {
    this.speedMax = speed;
    this.motion.setSpeedMax(speed);
} 
, setFeed: function(feed) {
    this.feed = feed;
    this.motion.setFeed(feed);
}
, setFrequency: function(frequency) {
    this.driver.setFrequency(frequency);
}
, setFillAngle: function(angle) {
    this.fillAngle = angle;
    this.fillRadius = this.fillAngle * Math.PI / 180;
  }
, setFillAngleTransform : function() {
    if (this.fillAngle && this.fillAngle != 0) {
      let angleMatrix = new Matrix();
      angleMatrix = angleMatrix.rotate(-this.fillRadius);
      this.matrix = angleMatrix.concat(this.matrix);
    }
  }
, setOptimizeParams : function(params) {
    this.optimizeParams = Object.assign({}, params);
    this.motion.setOptimizeParams(params);
  }
, setRotateMotion: function(params) {
    this.rotateMotionParams = Object.assign({}, params);
    this.motion.setRotateMotion(params);
}
, filter: function(fn) {
    this.filters.push(fn);
  }
, map: function(from, to) {
    if(arguments.length == 1) {
      to = from;
      from = 'xyza';
    }

    from = from.split('');
    to = to.split('');

    this.filter(function(p) {
      var tmp = {};
      var negative = false;
      var i = 0;

      to.forEach(function(d) {
        if(d == '-') {
          negative = true;
        }
        else {
          tmp[d] = p[ from[i] ];

          if(tmp[d] != undefined && negative) {
            tmp[d] = -tmp[d];
          }

          negative = false;
          i++;
        }
      });

      return tmp;
    });
  }
, beginPath: function() {
    // this.prevsubPaths = this.subPaths;
    this.path = new Path();
    // this.subPaths = [this.path];
  }
, _restorePath: function() {
    this.subPaths = this.prevsubPaths;
    this.path = this.subPaths[this.subPaths.length-1] || new Path();
  }
, transform: function(a, b, c, d, e, f) {
    this.matrix = this.matrix.concat(
      new Matrix(a, b, c, d, e, f)
    );
  }
, setTransform: function(a, b, c, d, e, f) {
    this.matrix = new Matrix(a, b, c, d, e, f);
  }
, resetTransform: function() {
    this.matrix = new Matrix();
  }
, rotate: function(angle) {
    this.matrix = this.matrix.rotate(angle);
  }
, translate: function(x,y) {
    this.matrix = this.matrix.translate(x,y);
  }
, scale: function(x,y) {
    this.matrix = this.matrix.scale(x,y);
  }
  // TODO: clean up
, _transformPoint: function(a, i) {
    i = i || 0;
    if(a.length) {
      var v = new Point(a[i], a[i+1]);
      v = this.matrix.transformPoint(v);
      a[i] = v.x; 
      a[i+1] = v.y; 
    }
    else if(a.x !== undefined) {
      var v = new Point(a.x, a.y);
      v = this.matrix.transformPoint(v);
      a.x = v.x; 
      a.y = v.y; 
    }
  }
, _ensurePath: function(x,y) {
    if(this.path.subPaths.length === 0) {
      this.path.moveTo(x,y);
    }
  }
, moveTo: function(x,y) {
    this._transformPoint(arguments);
    this.path.moveTo(x,y);
  }
, lineTo: function(x,y) {
    this._transformPoint(arguments);
    this._ensurePath(x,y);
    this.path.lineTo(x,y);
  }
, arcTo: function(x1, y1, x2, y2, radius) {
    this._transformPoint(arguments,0);
    this._transformPoint(arguments,2);
    this._ensurePath(x1,y1);

    var p0 = this.path.lastPoint();
    var p1 = new Point(x1,y1);
    var p2 = new Point(x2,y2);
    var v01 = p0.sub(p1);
    var v21 = p2.sub(p1);

    // sin(A - B) = sin(A) * cos(B) - sin(B) * cos(A)
    var cross = v01.x * v21.y - v01.y * v21.x;

    if (Math.abs(cross) < 1E-10) {
        // on one line
        this.lineTo(x1,y1);
        return;
    }

    var d01 = v01.magnitude();
    var d21 = v21.magnitude();
    var angle = (Math.PI - Math.abs(Math.asin(cross / (d01 * d21)))) / 2;
    var span = radius * Math.tan(angle);
    var rate = span / d01;

    var startPoint = new Point(
      p1.x + v01.x * rate,
      p1.y + v01.y * rate
    );

    rate = span / d21; 

    var endPoint = new Point(
      p1.x + v21.x * rate,
      p1.y + v21.y * rate
    );

    var midPoint = new Point(
      (startPoint.x + endPoint.x) / 2,
      (startPoint.y + endPoint.y) / 2
    );

    var vm1 = midPoint.sub(p1);
    var dm1 = vm1.magnitude();
    var d = Math.sqrt(radius*radius + span*span);

    var centerPoint = new Point();
    rate = d / dm1;
    centerPoint.x = p1.x + vm1.x * rate;
    centerPoint.y = p1.y + vm1.y * rate;

    var arc = utils.pointsToArc(centerPoint, startPoint, endPoint);

    this.path.lineTo(startPoint.x, startPoint.y);
    this.path.arc(centerPoint.x, centerPoint.y, arc.radius, arc.start, arc.end, cross > 0);
  }
, arc: function (x, y, radius,
									  aStartAngle,
                    aEndAngle,
                    aClockwise ) {

    // In the conversion to points we lose the distinction
    // between 0 and pi2 so we must optimize out 0 here 
    // or else they will be treated as full circles.

    if(aStartAngle - aEndAngle === 0) {
      return;
    }

    // See portal2 example
    if(aEndAngle-aStartAngle === -Math.PI*2)
      aEndAngle = Math.PI*2;

    var center = new Point(x, y);
    var points = utils.arcToPoints(x, y,
                                   aStartAngle,
                                   aEndAngle,
                                   radius);

    this._transformPoint(center);
    this._transformPoint(points.start);
    this._transformPoint(points.end);

    var res = utils.pointsToArc(center,
                                points.start,
                                points.end);

    // this._ensurePath(points.start.x, points.start.y);

    this.path.arc(center.x, center.y, res.radius, res.start, res.end, aClockwise);
    
    // var tmp = new Path();
    // tmp.moveTo(points.start.x, points.start.y);
    // tmp.arc(center.x, center.y, radius, res.start, res.end, aClockwise);

    // tmp.getPoints(40).forEach(function(p) {
    //   this.lineTo(p.x,p.y);
    // },this);
  }
, circle: function(x, y, rad, ccw) {
    this.arc(x, y, rad, 0, Math.PI*2, ccw);
  }
, bezierCurveTo: function( aCP1x, aCP1y,
                           aCP2x, aCP2y,
                           aX, aY ) {

    this._transformPoint(arguments, 0);
    this._transformPoint(arguments, 2);
    this._transformPoint(arguments, 4);

    this.path.bezierCurveTo.apply(this.path, arguments);
  }
, quadraticCurveTo: function( aCPx, aCPy, aX, aY ) {
    this._transformPoint(arguments, 0);
    this._transformPoint(arguments, 2);

    this.path.quadraticCurveTo.apply(this.path, arguments);
  }
, clip: function() {
    this.clipRegion = this.path;
  }
, rect: function(x,y,w,h) {
    this.moveTo(x,y);
    this.lineTo(x+w,y);
    this.lineTo(x+w,y+h);
    this.lineTo(x,y+h);
    this.closePath();
  }
, fillRect: function(x,y,w,h,speed) { 
    this.save();
    this.beginPath();
    this.speed = speed || this.speed;
    this.rect(x,y,w,h);
    this.fill();
    this.restore();
  }
, fillCircle: function(x,y,rad,speed) { 
    this.save();
    this.beginPath();
    this.speed = speed || this.speed;
    this.circle(x,y,rad);
    this.fill();
    this.restore();
  }
, clone: function(driver) {
    var copy = new GCanvas(driver);
    this.save();
    copy.stack[0] = this.stack[0];
    copy.restore();
    return copy;
  }
, measureText: function(text) {
    var copy = this.clone(null);
    copy.text(text);
    var b = copy.path.getBounds();

    b.width = b.right - b.left;
    b.height = b.bottom - b.top;

    return b;
  }
, _isOpaque: function(color) {
    if(color == 'transparent') return false;
    if(color == 'none') return false;

    if(typeof color == 'string' &&
       color.match(/rgba\((?:.*,){3}[0\.]*\)/)) {
      return false;
    }

    return true;
  }
  // 绘制直线路径
, stroke: function(align, speed) {
    // 确认画笔不透明
    // if(!this._isOpaque(this.strokeStyle)) return;

    this.save();

    // 单独传递一个数字参数来设置 speed，而不需要显式地传递 align
    if(typeof align === 'number') {
      speed = align;
      align = undefined;
    }

    if(speed) {
      this.speed = speed;
    }

    var offset = 0;

    align = align || this.align;

    if(align === 'outer') {
      offset = this.toolDiameter/2;
    }
    if(align === 'inner') {
      offset = -this.toolDiameter/2;
    }

    var path = this.path;

    if(align != 'center') {
      path = path.simplify('evenodd', this.precision);
      path = path.offset(offset);
    }

    if (this.rotateMotionParams && this.rotateMotionParams.enable) {
      path = path.rotateMode(this.rotateMotionParams.yAxisStepLength);
      this.motion.followPathRotate(path);
    } else {
      if (path.subPaths) {
        path.subPaths.forEach(function (subPath) {

          // Climb milling
          if (align == 'inner') {
            subPath = subPath.reverse();
          }

          this._layer(subPath, function (speed) {
            this.motion.followPath(subPath, speed);
          });
        }, this);
      }
    }
    // this.motion.laserPowerOff(); // 回退到原点
    this.restore();
  }
  // fillRule: 'nonzero' | 'evenodd'
  // speed: 
, setFillStyle: function(dir) { 
    this.fillDir = dir;
  }
, fillElement: function() {
    this.toFillPath.append(this.path.clone());
}
, fill: function(fillRule) {
    // 确认画笔不透明
    // if(!this._isOpaque(this.fillStyle)) return; 

    this.save();

    if(!fillRule) {
      fillRule = this.fillRule;
    }

    if(!this.toolDiameter) {
      throw 'You must set context.toolDiameter to use fill()'
    }
    var path = this.path;

    path = path.simplify(fillRule, this.precision);
    path = path.clip(this.clipRegion, 0, this.precision);
    path = path.fillPath(this.toolDiameter, this.precision, this.fillDir);
  
    // 如果有填充角度设置，需要将每个子路径旋转回来
    if ( this.fillAngle && this.fillAngle !== 0) {
      let angleMatrix = new Matrix();
      angleMatrix = angleMatrix.rotate(this.fillRadius);
      path.transform(angleMatrix);
    }
    
    
    // path = path.sort();
    // path = path.connectEnds(this.toolDiameter);
    var motion = this.motion;

    if(path.subPaths)
    path.subPaths.forEach(function(subPath) {
      this._layer(subPath, function(speed) {
        this.motion.followPath(subPath, speed);
        // this.motion.followPath(subPath, speed);
      });
    }, this);

    // this.motion.laserPowerOff();
    this.restore();
  }
, updateBoundingBox: function() {
  let bounds = this.path.getBounds();
  this.bounds.left = Math.min(this.bounds.left, bounds.left);
  this.bounds.top = Math.max(this.bounds.top, bounds.top);
  this.bounds.right = Math.max(this.bounds.right, bounds.right);
  this.bounds.bottom = Math.min(this.bounds.bottom, bounds.bottom);
}
, drawBounds: function() {
    let bounds = this.bounds;
    let drawPath = new Path();
    let thatPath = this.path;
    this.path = drawPath;
    // this.rect(bounds.left, 
    //           bounds.bottom, 
    //           bounds.right-bounds.left, 
    //           bounds.top-bounds.bottom); // 不能直接使用 rect ，因为它会再做一次点变换，
    // 而使用 this.path.getBounds() 是已经经过画布变换的轮廓
    this.path.moveTo(bounds.left, bounds.bottom); // 先移动到左下角
    this.path.lineTo(bounds.right, bounds.bottom); // 右下角
    this.path.lineTo(bounds.right, bounds.top); // 右上角
    this.path.lineTo(bounds.left, bounds.top); // 左上角
    this.path.close(); // 闭合路径
    this.motion.followPath(drawPath, this.speed);
    this.path = thatPath;
}
, rotateModePreviewLine: function() { 
  let bounds = this.bounds;
  this.beginPath();
  this.path.moveTo(null, bounds.top); // 旋转模式会自动对调XY轴的GCode代码
  this.path.lineTo(null, bounds.bottom); 
  this.motion.followPath(this.path);
  // this.path.moveTo(0, 0);
  this.restore();
}

, rotateModePreviewRect: function() {  // TODO: 新增最大角度限制
    let bounds = this.bounds;
    this.beginPath();
    this.path.moveTo(bounds.left, bounds.bottom); // 先移动到左下角
    this.path.lineTo(bounds.right, bounds.bottom); // 右下角
    this.path.lineTo(bounds.right, bounds.top); // 右上角
    this.path.lineTo(bounds.left, bounds.top); // 左上角
    this.path.close(); // 闭合路径
    // this.path = this.path.fillPath(this.toolDiameter, this.precision, 4); // 4 ： 垂直双向填充
    this.motion.followPathRotate(this.path);
    this.restore();
}

, _layer: function(subPath, fn) {
  fn.call(this, this.speed)
    // var depthOfCut = this.depthOfCut || this.speed;

    // if(depthOfCut === 0) {
    //   fn.call(this, -this.top);
    //   return;
    // }

    // var invertedZ = this.speed < 0;
    // if (invertedZ && depthOfCut > 0) {
    //   depthOfCut = -depthOfCut;
    // }

    // var steps = Math.ceil(Math.abs(this.speed/depthOfCut));


    // var offset = -this.top;
    // while(steps--) {
    //   offset -= depthOfCut;

    //   // Clip to actual speed
    //   if (invertedZ) {
    //     offset = Math.max(offset, this.top+this.speed);
    //   } else {
    //     offset = Math.max(offset, -this.top-this.speed);
    //   }

    //   // Remove the material at this speed
    //   fn.call(this, offset);
    // }

    // // Finishing pass
    // if(this.ramping && subPath.isClosed()) {
    //   fn.call(this, offset);
    // }
  }
, text: function(text, x, y, params) {
    var fontProps = parseFont(this.font);
    var font = new Font(fontProps);

    // this.beginPath();
    this.save();
    // this.translate(x, y);
    this.transform(1, 0, 0, -1, x, y); // TODO: 未知原因：需要先进行 y = 0 的轴线进行翻转，再平移svg的坐标属性
    font.drawText(this, text);
    this.restore();
  }
, fillText: function(text, x, y, params) {
    this.text(text, x, y, params);
    this.fill();
  }
, strokeText: function(text, x, y, params) {
    this.text(text, x, y, params);
    this.stroke();
  }
, clearRect: function() {}
, closePath: function() {
    this.path.close();
  }
};

GCanvas.GcodeDriver = GcodeDriver;

var helvetiker = require('./fonts/helvetiker_regular.typeface');
Font.load(helvetiker);

